KiteBot Railway one-click deploy (Phase 3) — IaC topology + deploy docs - #7
Merged
Conversation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on plan Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…, runtime deps
- notion-mcp: pin NOTION_MCP_PORT=3001; agent dials ${{notion-mcp.NOTION_MCP_PORT}}
(the sidecar binds NOTION_MCP_PORT, never Railway's $PORT — previous
${{notion-mcp.PORT}} pointed at a port nothing listened on).
- agent: fold agent/railway.toml into the IaC build/deploy config and delete the
toml — Railway forbids a service being managed by both IaC and config-as-code.
Carries builder=NIXPACKS, healthcheckTimeout=300, restartPolicy ON_FAILURE.
- agent: pin PORT=8123 so uvicorn's bind and ${{agent.PORT}} always agree.
- agent: bind uvicorn --host :: for Railway dual-stack/IPv6 private networking.
- deps: move tsx + dotenv to dependencies (runtime-required by the channel and
notion-mcp services; would be pruned from a production-only install).
- docs: correct spec's notion-mcp port claim and INTELLIGENCE_CHANNEL_NAME
classification; note OPENAI_MODEL default in README.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…, doc sync
- sidecar: scripts/start-notion-mcp.ts gains NOTION_MCP_HOST (validated, default
127.0.0.1); IaC sets it to :: so the notion-mcp server (upstream default
127.0.0.1) is reachable over Railway's IPv6 private network. Without this the
agent's cross-container Notion connection was refused even with the port fixed.
- OPENAI_MODEL: stop managing it in the IaC (the agent defaults to gpt-5.5) so a
UI override survives 'railway config apply' — was a plain value that a re-apply
would silently clobber, contradicting the README's 'override in Variables' note.
- NOTION_MCP_AUTH_TOKEN: agent references ${{notion-mcp.NOTION_MCP_AUTH_TOKEN}}
(single source of truth) instead of a second hand-matched secret.
- notion-mcp: add ON_FAILURE restart policy; document that it requires its two
tokens and how to omit the service if you don't want Notion.
- deps: move @notionhq/notion-mcp-server to dependencies (runtime-required by the
deployed notion-mcp service; npx -y would otherwise fetch latest at cold start).
- docs: sync spec + plan to the shipped implementation (no agent/railway.toml,
IPv6 bind, port pinning, model unmanaged); fix README PORT/SERVER_PORT order,
Tavily-vs-Responses web-search note, and channels-intelligence optionality.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on, invariant hardening scripts/start-notion-mcp.ts: - Stop injecting a global Notion-Version header: @notionhq/notion-mcp-server >=2.4 sources the API version per-operation from its OpenAPI spec, and a globally-configured Notion-Version OVERRIDES those defaults (verified in the installed 2.4.1 http-client.buildDefaultHeaders), pinning every call to a 3-year-old version and breaking newer endpoints. NOTION_VERSION still forces one if needed. - Exit handler distinguishes a forwarded shutdown (SIGINT/SIGTERM → exit 0) from an abnormal signal death (→ exit 1), so a clean stop no longer trips the notion-mcp ON_FAILURE restart / reads as a crash in logs. - Add a SIGKILL escalation timeout on shutdown (child may ignore the signal, or shell:true may not propagate it). - Tighten NOTION_MCP_HOST validation to reject a leading '-' (flag-injection shape). - Correct the header docblock to describe the actual inbound/outbound token mapping. .railway/railway.ts: - Leave INTELLIGENCE_CHANNEL_NAME unmanaged (app/managed.ts defaults it to 'kitebot') so a UI override survives config apply — same pattern as OPENAI_MODEL. - Fix model-default citation to agent/agent.py. agent/main.py: default SERVER_HOST to :: so the pnpm-agent/python-main path also honors the IPv6 private-networking invariant (dual-stack; local still works). docs: sync spec/plan to the shipped file (resources order, channel-name note). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…/comments
scripts/start-notion-mcp.ts:
- Exit handler: check shuttingDown BEFORE the exit code, so a forwarded-signal
shutdown that shell:true surfaces as a numeric 128+signum code (e.g. 143) still
reports success and doesn't trip the notion-mcp ON_FAILURE restart policy.
- Treat an empty NOTION_MCP_PORT/NOTION_MCP_HOST ('' from a bare .env line) as
unset → default, matching agent/main.py; clearer non-numeric port message.
- Correct the NOTION_TOKEN-deletion comment: OPENAPI_MCP_HEADERS takes precedence
(server checks it before NOTION_TOKEN), so the delete is defensive cleanup.
- Note both NOTION_MCP_URL consumers (runtime.ts and the Python agent).
package.json: raise @notionhq/notion-mcp-server floor to ^2.4.0 — the launcher
relies on >=2.4 per-operation Notion-Version sourcing; ^2.2.1 allowed sub-2.4.
agent/main.py: make CORS origins env-driven (CORS_ALLOW_ORIGINS, default '*') so a
public exposure can be locked down; clarify the local-dev-only PORT block comment.
docs: README adds LINEAR_API_KEY to the .env example and clarifies the deploy flow
(pnpm install first so the CLI can evaluate the config; railway init to create);
spec drops the stale agent NOTION_MCP_AUTH_TOKEN secret and the OPENAI_MODEL/
INTELLIGENCE_CHANNEL_NAME wiring lines (now unmanaged); plan deploy block synced.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ups, macOS bind scripts/start-notion-mcp.ts: - Spawn with shell only on Windows (npx.cmd/CVE-2024-27980); on POSIX spawn npx directly + detached, so the server runs in its own process group. Shutdown now signals the whole group (process.kill(-pid)) with a SIGKILL escalation, which reaps the underlying node server instead of just the shell wrapper and also removes the terminal-Ctrl-C race (the detached child no longer receives the terminal's SIGINT directly). - Exit handler: any termination we did NOT initiate is a failure — exit non-zero even on code 0 (e.g. server exits after a bad flag) so Railway's ON_FAILURE restart fires and it's visible, instead of a silent code-0 passthrough. - Header docstring names both consumers (runtime.ts + Python agent; Railway's sole consumer is the Python agent). agent/main.py: - Revert local SERVER_HOST default to 0.0.0.0 — a :: default risked local macOS dev (a :: socket may not accept 127.0.0.1). Railway still binds :: via the startCommand; this __main__ path is local-only. - CORS: treat an empty/blank CORS_ALLOW_ORIGINS as unset -> '*' (an empty allowlist would otherwise block every origin, contradicting the default). docs: README documents the Notion cold-start race + redeploy workaround; spec fixes the INTELLIGENCE_CHANNEL_NAME self-contradiction (it is NOT set in the IaC); package.json dependency ordering tidied. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rce warnings, parity agent/main.py: - CORS: fall back to ['*'] when the resolved allowlist is empty, so a whitespace/comma-only CORS_ALLOW_ORIGINS no longer blocks every origin. - SERVER_HOST / SERVER_PORT: treat set-but-empty as unset (`getenv(x) or default`), matching the convention already used for PORT/CORS. - Route [ERROR] diagnostics to stderr. agent/tools.py: when an internal source is CONFIGURED (token set) but its MCP server times out / is unavailable, warn loudly on stderr that the agent is running WITHOUT those tools — distinguishing a real degradation (e.g. the Railway cold-start race) from a source the operator intentionally left disabled. .railway/railway.ts: give the channel service the same ON_FAILURE restart policy as agent/notion-mcp (parity — don't leave KiteBot silently offline on a crash). scripts/start-notion-mcp.ts: correct the validation comments (`shell: isWindows`, not an unconditional `shell: true`). docs: reconcile the spec's deploy commands with the README (railway init to create a project, or railway link for an existing one). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…an channel restart-policy back-port Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jerelvelarde
added a commit
that referenced
this pull request
Jul 27, 2026
`.migration-discovery.md` was a scratch artifact sitting at the repo root, ungitignored, and contradicted by the PR that carried it: it records the runtime floor staying at ^1.62.3 and channels-intelligence as a direct dep, while the shipped branch raises the floor to ^1.63.2 and drops that dep. Its 571 lines of verified SDK signatures are worth keeping, so move it beside the design doc and plan under docs/superpowers/specs/ (the convention PRs #6 and #7 established) and head it with a note marking it a point-in-time record plus the two places the implementation deliberately diverged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
A Railway one-click deploy for the full KiteBot stack (Phase 3, builds on the merged Phases 1 & 2 in #6). The whole stack deploys as three services wired over Railway private networking, all from this one repo:
agentagent/)/health(rootagent/)notion-mcppnpm notion-mcp)channelpnpm channel)Deliverables
.railway/railway.ts— Infrastructure-as-Code topology (railwaySDK v3.5.7): 3 services, reference-variable wiring (AGENT_URL,NOTION_MCP_URL), pinned ports,::binds for IPv6 private networking, secrets viapreserve(), per-service restart policy. Applied withrailway config apply.tsx/dotenv/@notionhq/notion-mcp-servermoved todependencies(runtime-required by the deployed services);scripts/start-notion-mcp.tsgains aNOTION_MCP_HOSTarg so the sidecar binds::on Railway.Notably, there is deliberately no
agent/railway.toml— Railway forbids a service being managed by both IaC and config-as-code, so the agent's build+deploy config lives only in the IaC.Review
Ran a full 7-agent unbiased CR to convergence (6 fix rounds + confirmations). The deploy files (
.railway/railway.ts,scripts/start-notion-mcp.ts) are clean. The loop fixed: the notion-mcp port mismatch, dual IaC/config-as-code management, IPv6 binds, runtime-dep misclassification, a staleNotion-Versionpin, a local-macOS bind regression, CORS edge cases, and supervisor exit/signal semantics.Deferred to a follow-up (out of scope here)
The CR also surfaced pre-existing Phase-1/2 agent-backend issues (unrelated to the deploy) — most importantly a
recursion_limit: 100that's silently ignored on the AG-UI streaming path, plusresearch()error-handling and missing test coverage — tracked as a separate follow-up.Deploy-time verification (needs a live Railway deploy — the deployer's step)
builder: "NIXPACKS"resolves theuv/Python agent (Railway's default is now Railpack; Dockerfile fallback is the hedge).pnpm-launched services.Test plan
pnpm check-types,pnpm test(108 passing),agentpytest (9 passing), and the IaC type-check all green. Live Slack + Railway deploy remain manual (need creds).🤖 Generated with Claude Code